#region Using Statements

using System;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Web;
using System.Web.SessionState;
using Dotss.Core;

#endregion

namespace Dotss.Client
{

	public class Client : ClientProxy
	{

		#region Fields

		#endregion
		
		#region Overrides of SessionStateStoreProviderBase

		/// <summary>
		/// Releases all resources used by the <see cref="T:System.Web.SessionState.SessionStateStoreProviderBase" /> implementation.
		/// </summary>
		public override void Dispose() { }

		/// <summary>
		/// Sets a reference to the <see cref="T:System.Web.SessionState.SessionStateItemExpireCallback" /> delegate for the Session_OnEnd event defined in the Global.asax file.
		/// </summary>
		/// <returns>
		/// true if the session-state store provider supports calling the Session_OnEnd event; otherwise, false.
		/// </returns>
		/// <param name="expireCallback">The <see cref="T:System.Web.SessionState.SessionStateItemExpireCallback" />  delegate for the Session_OnEnd event defined in the Global.asax file.</param>
		public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback)
		{
			return false;
		}

		/// <summary>
		/// Called by the <see cref="T:System.Web.SessionState.SessionStateModule" /> object for per-request initialization.
		/// </summary>
		/// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param>
		public override void InitializeRequest(HttpContext context) { }

		/// <summary>
		/// Returns read-only session-state data from the session data store.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.Web.SessionState.SessionStateStoreData" /> populated with session values and information from the session data store.
		/// </returns>
		/// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param>
		/// <param name="id">The <see cref="P:System.Web.SessionState.HttpSessionState.SessionID" /> for the current request.</param>
		/// <param name="locked">When this method returns, contains a Boolean value that is set to true if the requested session item is locked at the session data store; otherwise, false.</param>
		/// <param name="lockAge">When this method returns, contains a <see cref="T:System.TimeSpan" /> object that is set to the amount of time that an item in the session data store has been locked.</param>
		/// <param name="lockId">When this method returns, contains an object that is set to the lock identifier for the current request. For details on the lock identifier, see "Locking Session-Store Data" in the <see cref="T:System.Web.SessionState.SessionStateStoreProviderBase" /> class summary.</param>
		/// <param name="actions">When this method returns, contains one of the <see cref="T:System.Web.SessionState.SessionStateActions" /> values, indicating whether the current session is an uninitialized, cookieless session.</param>
		public override SessionStateStoreData GetItem(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
		{
			int outLockId;
			byte[] ServerBytes = Server.GetItem(
                new SessionArgs(Environment.MachineName, ClientConfig.Settings.ApplicationName, ClientConfig.Settings.EnvironmentName, id, null, ClientConfig.Settings.Timeout), false, out locked, out lockAge, out outLockId, out actions);

			lockId = outLockId;
			return Deserialize(context, ServerBytes, ClientConfig.Settings.Timeout);
		}

		/// <summary>
		/// Returns read-only session-state data from the session data store.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.Web.SessionState.SessionStateStoreData" /> populated with session values and information from the session data store.
		/// </returns>
		/// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param>
		/// <param name="id">The <see cref="P:System.Web.SessionState.HttpSessionState.SessionID" /> for the current request.</param>
		/// <param name="locked">When this method returns, contains a Boolean value that is set to true if a lock is successfully obtained; otherwise, false.</param>
		/// <param name="lockAge">When this method returns, contains a <see cref="T:System.TimeSpan" /> object that is set to the amount of time that an item in the session data store has been locked.</param>
		/// <param name="lockId">When this method returns, contains an object that is set to the lock identifier for the current request. For details on the lock identifier, see "Locking Session-Store Data" in the <see cref="T:System.Web.SessionState.SessionStateStoreProviderBase" /> class summary.</param>
		/// <param name="actions">When this method returns, contains one of the <see cref="T:System.Web.SessionState.SessionStateActions" /> values, indicating whether the current session is an uninitialized, cookieless session.</param>
		public override SessionStateStoreData GetItemExclusive(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
		{

			int outLockId;
			byte[] ServerBytes = Server.GetItem(
                new SessionArgs(Environment.MachineName, ClientConfig.Settings.ApplicationName, ClientConfig.Settings.EnvironmentName, id, null, ClientConfig.Settings.Timeout), true, out locked, out lockAge, out outLockId, out actions);

			lockId = outLockId;
			return Deserialize(context, ServerBytes, ClientConfig.Settings.Timeout);

		}

		/// <summary>
		/// Releases a lock on an item in the session data store.
		/// </summary>
		/// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param>
		/// <param name="id">The session identifier for the current request.</param>
		/// <param name="lockId">The lock identifier for the current request. </param>
		public override void ReleaseItemExclusive(HttpContext context, string id, object lockId)
		{
            Server.ReleaseLock(new SessionArgs(Environment.MachineName, ClientConfig.Settings.ApplicationName, ClientConfig.Settings.EnvironmentName, id, lockId, ClientConfig.Settings.Timeout));
		}

		/// <summary>
		/// Updates the session-item information in the session-state data store with values from the current request, and clears the lock on the data.
		/// </summary>
		/// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param>
		/// <param name="id">The session identifier for the current request.</param>
		/// <param name="item">The <see cref="T:System.Web.SessionState.SessionStateStoreData" /> object that contains the current session values to be stored.</param>
		/// <param name="lockId">The lock identifier for the current request. </param>
		/// <param name="newItem">true to identify the session item as a new item; false to identify the session item as an existing item.</param>
		public override void SetAndReleaseItemExclusive(HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem)
		{
			Server.SetItem(new SessionArgs(Environment.MachineName, ClientConfig.Settings.ApplicationName, ClientConfig.Settings.EnvironmentName, id, lockId, ClientConfig.Settings.Timeout), Serialize((SessionStateItemCollection)item.Items));
		}

		/// <summary>
		/// Deletes item data from the session data store.
		/// </summary>
		/// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param>
		/// <param name="id">The session identifier for the current request.</param>
		/// <param name="lockId">The lock identifier for the current request.</param>
		/// <param name="item">The <see cref="T:System.Web.SessionState.SessionStateStoreData" /> that represents the item to delete from the data store.</param>
		public override void RemoveItem(HttpContext context, string id, object lockId, SessionStateStoreData item)
		{
			Server.Remove(new SessionArgs(Environment.MachineName, ClientConfig.Settings.ApplicationName, ClientConfig.Settings.EnvironmentName, id, lockId, ClientConfig.Settings.Timeout));
		}

		/// <summary>
		/// Updates the expiration date and time of an item in the session data store.
		/// </summary>
		/// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param>
		/// <param name="id">The session identifier for the current request.</param>
		public override void ResetItemTimeout(HttpContext context, string id)
		{
            Server.ResetTimeout(new SessionArgs(Environment.MachineName, ClientConfig.Settings.ApplicationName, ClientConfig.Settings.EnvironmentName, id, null, ClientConfig.Settings.Timeout));
		}

		/// <summary>
		/// Creates a new <see cref="T:System.Web.SessionState.SessionStateStoreData" /> object to be used for the current request.
		/// </summary>
		/// <returns>
		/// A new <see cref="T:System.Web.SessionState.SessionStateStoreData" /> for the current request.
		/// </returns>
		/// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param>
		/// <param name="timeout">The session-state <see cref="P:System.Web.SessionState.HttpSessionState.Timeout" /> value for the new <see cref="T:System.Web.SessionState.SessionStateStoreData" />.</param>
		public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout)
		{
			return new SessionStateStoreData(new SessionStateItemCollection(), SessionStateUtility.GetSessionStaticObjects(context), timeout);
		}

		/// <summary>
		/// Adds a new session-state item to the data store.
		/// </summary>
		/// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param>
		/// <param name="id">The <see cref="P:System.Web.SessionState.HttpSessionState.SessionID" /> for the current request.</param>
		/// <param name="timeout">The session <see cref="P:System.Web.SessionState.HttpSessionState.Timeout" /> for the current request.</param>
		public override void CreateUninitializedItem(HttpContext context, string id, int timeout)
		{
			Server.CreateNew(new SessionArgs(System.Environment.MachineName, ClientConfig.Settings.ApplicationName, ClientConfig.Settings.EnvironmentName, id, null, timeout));
		}

		/// <summary>
		/// Called by the <see cref="T:System.Web.SessionState.SessionStateModule" /> object at the end of a request.
		/// </summary>
		/// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param>
		public override void EndRequest(HttpContext context) { }

		#endregion

		#region Private Methods

		private static byte[] Serialize(SessionStateItemCollection items)
		{
			
			// Configure the streams to write the items
			MemoryStream ms			= new MemoryStream();
			BinaryWriter writer		= new BinaryWriter(ms);

			if (items != null)
			{
				try
				{
					items.Serialize(writer);
					writer.Flush();
				} catch (Exception ex)
				{
					throw new ApplicationException("Unable to serialize objects to DOTSS", ex);
				}
			}

			return ClientConfig.Settings.Compression ? CompressStream(ms) : ms.ToArray();

		}

		private static SessionStateStoreData Deserialize(HttpContext context, byte[] items, int timeout)
		{

			MemoryStream ms			= ClientConfig.Settings.Compression ? DecompressBytes(items) : new MemoryStream(items);

			BinaryReader reader		= new BinaryReader(ms);
			ms.Position				= 0;

			return new SessionStateStoreData(SessionStateItemCollection.Deserialize(reader), SessionStateUtility.GetSessionStaticObjects(context), timeout);

		}

		/// <summary>
		/// Compress the stream of bytes using a standard GZip mechanism
		/// </summary>
		/// <param name="items">Array of bytes to compress</param>
		/// <returns>Compressed stream of the bytes submitted</returns>
		private static MemoryStream DecompressBytes(byte[] items)
		{

			MemoryStream ms						= new MemoryStream();
			MemoryStream decompressedItems		= new MemoryStream(items);

			decompressedItems.Position			= 0;
			GZipStream gz						= new GZipStream(decompressedItems, CompressionMode.Decompress);

			byte[] buffer						= new byte[1024];
			int bytesread						= gz.Read(buffer, 0, buffer.Length);

			while (bytesread > 0)
			{

				ms.Write(buffer, 0, bytesread);

				if (bytesread < buffer.Length) break;

				bytesread = gz.Read(buffer, 0, buffer.Length);

			}

			return ms;

		}

		/// <summary>
		/// Compress a stream to a byte array using GZip
		/// </summary>
		/// <param name="ms">Stream to compress</param>
		/// <returns>Compressed array of bytes</returns>
		private static byte[] CompressStream(MemoryStream ms)
		{

			long originalLen = ms.Length;

			// Compress!
			MemoryStream compressed = new MemoryStream();
			GZipStream gz = new GZipStream(compressed, CompressionMode.Compress);
			gz.Write(ms.ToArray(), 0, ms.ToArray().Length);
			gz.Flush();
			gz.Close();

			byte[] outArray = compressed.ToArray();
			Debug.WriteLine(string.Format("Compressed {0} bytes to {1} bytes: {2:0.00%}", originalLen, outArray.Length,
										  (originalLen - outArray.Length) / (double)originalLen));

			return outArray;
		}

		#endregion

	}

}
